/*
 * Copyright (c) 2010, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "./DirectX9Private.h"

#include "alkes/directx9/DirectXState.h"

namespace alkes {

#define ST(name, type)  D3DRS_##name,
#undef AL_DX_ST_B
#undef AL_DX_ST_F
#undef AL_DX_ST
#define AL_DX_ST_B(name, type) ST(name, type)
#define AL_DX_ST_F(name, type)
#define AL_DX_ST(name, type)
const static D3DRENDERSTATETYPE s_state_type_b[] = {
#include "alkes/directx9/RenderState.inl"
};

#undef AL_DX_ST_B
#undef AL_DX_ST_F
#undef AL_DX_ST
#define AL_DX_ST_B(name, type)
#define AL_DX_ST_F(name, type) ST(name, type)
#define AL_DX_ST(name, type)
const static D3DRENDERSTATETYPE s_state_type_f[] = {
#include "alkes/directx9/RenderState.inl"
};

#undef AL_DX_ST_B
#undef AL_DX_ST_F
#undef AL_DX_ST
#define AL_DX_ST_B(name, type)
#define AL_DX_ST_F(name, type)
#define AL_DX_ST(name, type)   ST(name, type)
const static D3DRENDERSTATETYPE s_state_type[] = {
#include "alkes/directx9/RenderState.inl"
};

inline int getIndex(RenderStateBool state)
{
    return state;
}

inline int getIndex(RenderStateFloat state)
{
    return AL_DX_RS_RENDER_STATE_BOOL_MAX + state;
}

inline int getIndex(RenderState state)
{
    return AL_DX_RS_RENDER_STATE_BOOL_MAX + AL_DX_RS_RENDER_STATE_FLOAT_MAX + state;
}

template<class T, class U>
bool knows(T state, const U& flags)
{
    int i = getIndex(state);
    return flags[i];
}

DirectXState::DirectXState(IDirect3DDevice* device)
: device_(device)
{
}

DirectXState::~DirectXState()
{
}

void DirectXState::setRenderState(RenderStateBool state, bool value)
{
    bool know = knows(state, loaded_flags_);
    if (!know || bool_states_[state] != value)
    {
        device_->SetRenderState(s_state_type_b[state], value ? TRUE : FALSE);
        LOGV("SetRenderState(%d, (BOOL)%d)", state, value);

        bool_states_.setFlag(state, value);

        if (!know)
            loaded_flags_.setFlag(getIndex(state));
    }
}

void DirectXState::setRenderState(RenderStateFloat state, float value)
{
    bool know = knows(state, loaded_flags_);
    if (!know || float_states_[state] != value)
    {
        device_->SetRenderState(s_state_type_f[state], *reinterpret_cast<DWORD*>(&value));
        LOGV("SetRenderState(%d, (float)%f)", state, value);

        float_states_[state] = value;

        if (!know)
            loaded_flags_.setFlag(getIndex(state));
    }
}

void DirectXState::setRenderState(RenderState state, DWORD value)
{
    bool know = knows(state, loaded_flags_);
    if (!know || dword_states_[state] != value)
    {
        device_->SetRenderState(s_state_type[state], value);
        LOGV("SetRenderState(%d, (DWORD)%d)", state, value);

        dword_states_[state] = value;

        if (!know)
            loaded_flags_.setFlag(getIndex(state));
    }
}

bool DirectXState::getRenderState(RenderStateBool state)
{
    bool know = knows(state, loaded_flags_);
    if (!know)
    {
        bool_states_.setFlag(state, static_cast<const DirectXState*>(this)->getRenderState(state));

        if (!know)
            loaded_flags_.setFlag(getIndex(state));
    }

    return bool_states_[state];
}

float DirectXState::getRenderState(RenderStateFloat state)
{
    bool know = knows(state, loaded_flags_);
    if (!know)
    {
        float_states_[state] = static_cast<const DirectXState*>(this)->getRenderState(state);

        if (!know)
            loaded_flags_.setFlag(getIndex(state));
    }

    return float_states_[state];
}

DWORD DirectXState::getRenderState(RenderState state)
{
    bool know = knows(state, loaded_flags_);
    if (!know)
    {
        dword_states_[state] = static_cast<const DirectXState*>(this)->getRenderState(state);

        if (!know)
            loaded_flags_.setFlag(getIndex(state));
    }

    return dword_states_[state];
}

bool DirectXState::getRenderState(RenderStateBool state) const
{
    if (knows(state, loaded_flags_))
        return bool_states_[state];
    else
    {
        DWORD value;
        device_->GetRenderState(s_state_type_b[state], &value);
        return FALSE != value;
    }
}

float DirectXState::getRenderState(RenderStateFloat state) const
{
    if (knows(state, loaded_flags_))
        return float_states_[state];
    else
    {
        DWORD value;
        device_->GetRenderState(s_state_type_f[state], &value);
        return *reinterpret_cast<float*>(&value);
    }
}

DWORD DirectXState::getRenderState(RenderState state) const
{
    if (knows(state, loaded_flags_))
        return dword_states_[state];
    else
    {
        DWORD value;
        device_->GetRenderState(s_state_type[state], &value);
        return value;
    }
}

}
